package io.openkunlun.javadsl.springboot3.server;

import com.google.common.collect.Lists;
import io.openkunlun.javadsl.springboot3.EnableDaprServer;
import io.openkunlun.javarpc.RpcHandler;
import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.filter.AssignableTypeFilter;

import java.lang.reflect.Field;
import java.util.*;

public class DaprHandlerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {

    private Environment environment;
    private ResourceLoader resourceLoader;

    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {

        if (!metadata.hasAnnotation(EnableDaprServer.class.getName())) {
            return;
        }

        Map<String, Object> annotationAttributesMap = metadata.getAnnotationAttributes(EnableDaprServer.class.getName());
        AnnotationAttributes annotationAttributes = Optional.ofNullable(AnnotationAttributes.fromMap(annotationAttributesMap)).orElseGet(AnnotationAttributes::new);
        Set<String> packages = retrievePackagesName(metadata, annotationAttributes);
        ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry, false, environment, resourceLoader);
        scanner.addIncludeFilter(new AssignableTypeFilter(RpcHandler.class));

        for (String needScanPackage : packages) {
            Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(needScanPackage);
            try {
                registerCandidateComponents(registry, candidateComponents);
            } catch (ClassNotFoundException e) {
                throw new BeanInitializationException("DaprHandler registration failed.", e);
            }
        }
    }

    private Set<String> retrievePackagesName(AnnotationMetadata annotationMetadata, AnnotationAttributes attributes) {
        String[] packages = attributes.getStringArray("basePackages");
        Set<String> packagesToScan = new LinkedHashSet<>(Arrays.asList(packages));
        if (packagesToScan.isEmpty()) {
            packagesToScan.add(getPackageName(annotationMetadata.getClassName()));
        }
        return packagesToScan;
    }

    private void registerCandidateComponents(BeanDefinitionRegistry registry, Set<BeanDefinition> candidateComponents) throws ClassNotFoundException {
        for (BeanDefinition candidateComponent : candidateComponents) {
            if (candidateComponent instanceof AnnotatedBeanDefinition) {
                AnnotatedBeanDefinition annotatedBeanDefinition = (AnnotatedBeanDefinition) candidateComponent;
                AnnotationMetadata annotationMetadata = annotatedBeanDefinition.getMetadata();
                Class<?> clazz = Class.forName(annotationMetadata.getClassName());
                List<Field> fields = Lists.newArrayList(clazz.getDeclaredFields());
                AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(DaprHandlerFactoryBean.class)
                        .addConstructorArgValue(clazz)
                        .addConstructorArgValue(fields)
                        .setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE)
                        .setRole(BeanDefinition.ROLE_INFRASTRUCTURE)
                        .getBeanDefinition();
                registry.registerBeanDefinition(clazz.getName(), beanDefinition);
            }
        }
    }


    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }

    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }

    static String getPackageName(String className) {
        if (null == className || className.isEmpty()) {
            return "";
        } else {
            while (className.charAt(0) == '[') {
                className = className.substring(1);
            }

            if (className.charAt(0) == 'L' && className.charAt(className.length() - 1) == ';') {
                className = className.substring(1);
            }

            int i = className.lastIndexOf(46);
            return i == -1 ? "" : className.substring(0, i);
        }
    }
}
